using System;
using System.Collections.Generic;
using System.Linq;
using Magiq.Delete;
using Magiq.Insert;
using Magiq.Query;
using Magiq.Support;
using Magiq.Update;

namespace Magiq
{
    public static class MagiqConfigurator
    {
        private static List<MagiqProvider> providers;

        static MagiqConfigurator()
        {
            Clear();
        }

        private static TStrategy GetStrategy<TStrategy>(Func<MagiqProvider, TStrategy> getStrategy)
            where TStrategy : class
        {
            var strategy = providers.Select(getStrategy).FirstOrDefault(x => x != null);
            if (strategy == null)
                throw new NotSupportedException(
                    "There is not strategy defined for the given operation. Are you sure you registered the provider using MagiqConfigurator.Register() method?");

            return strategy;
        }

        internal static IUpdateStrategy ForUpdate<T>(IUpdate<T> update) where T : class
        {
            return GetStrategy(x => x.GetUpdateStrategy(update));
        }

        internal static IDeleteStrategy ForDelete<T>(IDelete<T> delete) where T : class
        {
            return GetStrategy(x => x.GetDeleteStrategy(delete));
        }

        internal static IQueryStrategy<TChild> ForQuery<T, TChild>(QueryInfo<T, TChild> queryInfo) where T : class 
        {
            return GetStrategy(x => x.GetQueryStrategy(queryInfo));
        }

        internal static IInsertStrategy ForInsert<TSource, TDestination, TNew>(IInsert<TSource, TDestination, TNew> insert)
            where TDestination : class where TNew : class, TDestination, new() 
        {
            return GetStrategy(x => x.GetInsertStrategy(insert));
        }

        internal static IInsertStrategy ForBulkInsert<TSource, TDestination, TNew>(IInsert<TSource, TDestination, TNew> insert)
            where TDestination : class
            where TNew : class, TDestination, new()
        {
            return GetStrategy(x => x.GetBulkInsertStrategy(insert));
        }

        internal static IInsertDestination<TNew> ForInsertDestination<TSource, TDestination, TNew>(
            IInsert<TSource,TDestination, TNew> insert)
            where TDestination : class
            where TNew : class, TDestination, new()
        {
            return GetStrategy(x => x.GetInsertDestination(insert));
        }

        public static IMagiqConfigurator Register<TMagiq>() where TMagiq : MagiqProvider, new()
        {
            return new Configurator().Register<TMagiq>();
        }

        public static IMagiqConfigurator Register(MagiqProvider magiq)
        {
            return new Configurator().Register(magiq);
        }

        public static IMagiqConfigurator Clear()
        {
            providers = new List<MagiqProvider>();
            return new Configurator();
        }

        #region Nested type: Configurator

        private class Configurator : IMagiqConfigurator
        {
            public IMagiqConfigurator Register<TMagiq>() where TMagiq : MagiqProvider, new()
            {
                return Register(new TMagiq());
            }

            public IMagiqConfigurator Register(MagiqProvider magiq)
            {
                providers.Add(magiq);
                providers.Sort();
                return this;
            }
        }

        #endregion

        internal static IEnumerableStrategy<TNew> ForInsertSourceObjects<TSource, TNew>(InsertSource<TNew> source)
        {
            return GetStrategy(x => x.GetObjectSourceStrategy<TSource, TNew>(source));
        }
    }
}